using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using ParserGenerator.Extensions;
using ParserGenerator.Output;

namespace ParserGenerator.TableCompression
{
    [Serializable]
    public sealed class CompactTable
    {
        public int Columns { get; internal set; }
        public double LoadFactor { get; internal set; }

        public ReadOnlyCollection<CompactTableRow> Rows { get; internal set; }
        public ReadOnlyCollection<int> BaseArray { get; internal set; }
        public ReadOnlyCollection<int> CheckArray { get; internal set; }
        public ReadOnlyCollection<int> ValueArray { get; internal set; }

        public int RawBytes => Rows.Count * Columns * sizeof(int);
        public int UseBytes => (BaseArray.Count + CheckArray.Count + ValueArray.Count) * sizeof(int);
        public double CompressionRatio => (RawBytes > 0) ? (double)(UseBytes - RawBytes) / RawBytes : 0.0;

        public int this[int row, int column]
        {
            get
            {
                if (row < 0)
                    throw new ArgumentOutOfRangeException(nameof(row), SR.RowMustBeNonNegative);
                if (row >= Rows.Count)
                    throw new ArgumentOutOfRangeException(nameof(row), SR.RowOutOfRange);
                if (column < 0)
                    throw new ArgumentOutOfRangeException(nameof(column), SR.ColumnMustBeNonNegative);
                if (column >= Columns)
                    throw new ArgumentOutOfRangeException(nameof(column), SR.ColumnOutOfRange);
                int place = BaseArray[row];
                if (CheckArray[place + column] == column)
                    return ValueArray[place + column];
                else
                    return -1;
            }
        }

        internal CompactTable()
        {
        }

        public ReadOnlyCollection<ReadOnlyCollection<KeyValuePair<int, int>>> Reconstruct()
        {
            var rows = new ReadOnlyCollection<KeyValuePair<int, int>>[BaseArray.Count];
            for (int i = 0; i < rows.Length; i++)
            {
                var row = new List<KeyValuePair<int, int>>();
                for (int c = 0; c < Columns; c++)
                {
                    int place = BaseArray[i];
                    if (CheckArray[place + c] == c)
                        row.Add(new KeyValuePair<int, int>(c, ValueArray[place + c]));
                }
                rows[i] = row.ToArray().AsReadOnly();
            }
            return rows.AsReadOnly();
        }

        public void Jsonize(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.OpenCurlyBlock();
            writer.WriteLine("\"columns\": {0},", Columns);
            writer.WriteLine("\"loadFactor\": {0},", LoadFactor);
            writer.WriteLine("\"rows\": [");
            writer.OpenBlock();
            for (int index = 0; index < Rows.Count; index++)
            {
                if (index > 0)
                    writer.WriteLine(",");
                JsonizeTableRow(writer, Rows[index]);
            }
            writer.WriteLine("],");
            writer.CloseBlock();
            writer.Write("\"baseArray\": ");
            JsonizeIntArray(writer, BaseArray);
            writer.WriteLine(",");
            writer.Write("\"checkArray\": ");
            JsonizeIntArray(writer, CheckArray);
            writer.WriteLine(",");
            writer.Write("\"valueArray\": ");
            JsonizeIntArray(writer, ValueArray);
            writer.CloseCurlyBlock();
        }

        private void JsonizeTableRow(IndentedWriter writer, CompactTableRow row)
        {
            writer.Write("[");
            for (int i = 0; i < row.Cells.Count; i++)
            {
                if (i > 0)
                    writer.Write(",");
                CompactTableCell c = row.Cells[i];
                writer.Write("[{0}, {1}]", c.Column, c.Value);
            }
            writer.Write("]");
        }

        private void JsonizeIntArray(IndentedWriter writer, ReadOnlyCollection<int> items)
        {
            writer.Write("[");
            for (int i = 0; i < items.Count; i++)
            {
                if (i > 0)
                    writer.Write(", ");
                writer.Write(items[i]);
            }
            writer.Write("]");
        }

        public string ToJsonString()
        {
            using (StringWriter stream = new StringWriter())
            {
                IndentedWriter writer = new IndentedWriter(stream);
                stream.NewLine = "\n";
                Jsonize(writer);
                return stream.ToString().Trim();
            }
        }

        public sealed override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            double ratio = CompressionRatio * 100.0;
            string compressionRatio;
            if (ratio >= 0.0)
                compressionRatio = string.Format("+{0:g4}%", ratio);
            else
                compressionRatio = string.Format("{0:g4}%", ratio);
            sb.AppendFormat("rows: {0}", Rows.Count);
            sb.AppendFormat("\ncolumns: {0}", Columns);
            sb.AppendFormat("\nload_factor: {0:g4}%", LoadFactor * 100.0);
            sb.AppendFormat("\nraw_bytes: {0}", RawBytes);
            sb.AppendFormat("\nuse_bytes: {0}", UseBytes);
            sb.AppendFormat("\ncompression_ratio: {0}", compressionRatio);
            return sb.ToString();
        }
    }
}
